<!--

Part of this page is based on the OpenAI Chatbot example by David Härer:
https://github.com/david-haerer/chatapi

MIT License Copyright (c) 2023 David Härer
            Copyright (c) 2024-2025 Ettore Di Giacinto

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

-->
<!doctype html>
<html lang="en">
  {{template "views/partials/head" .}}
  <script src="static/assets/pdf.min.js"></script>
  <script>
    // Initialize PDF.js worker
    pdfjsLib.GlobalWorkerOptions.workerSrc = 'static/assets/pdf.worker.min.js';
  </script>
  <script>
    // Initialize Alpine store - must run before Alpine processes DOM
    // Get context size from template
    var __chatContextSize = null;
    {{ if .ContextSize }}
    __chatContextSize = {{ .ContextSize }};
    {{ end }}

    // Function to initialize store
    function __initChatStore() {
      if (!window.Alpine) return;
      
      // Check for MCP mode from localStorage (set by index page)
      // Note: We don't clear localStorage here - chat.js will handle that after reading all data
      let initialMcpMode = false;
      try {
        const chatData = localStorage.getItem('localai_index_chat_data');
        if (chatData) {
          const parsed = JSON.parse(chatData);
          if (parsed.mcpMode === true) {
            initialMcpMode = true;
          }
        }
      } catch (e) {
        console.error('Error reading MCP mode from localStorage:', e);
      }
      
      if (Alpine.store("chat")) {
        Alpine.store("chat").contextSize = __chatContextSize;
        Alpine.store("chat").mcpMode = initialMcpMode;
        return;
      }

      Alpine.store("chat", {
        history: [],
        languages: [undefined],
        systemPrompt: "",
        mcpMode: initialMcpMode,
        contextSize: __chatContextSize,
        tokenUsage: {
          promptTokens: 0,
          completionTokens: 0,
          totalTokens: 0,
          currentRequest: null
        },
        clear() {
          this.history.length = 0;
          this.tokenUsage = {
            promptTokens: 0,
            completionTokens: 0,
            totalTokens: 0,
            currentRequest: null
          };
        },
        updateTokenUsage(usage) {
          // Usage values in streaming responses are cumulative totals for the current request
          // We track session totals separately and only update when we see new (higher) values
          if (usage) {
            const currentRequest = this.tokenUsage.currentRequest || {
              promptTokens: 0,
              completionTokens: 0,
              totalTokens: 0
            };
            
            // Check if this is a new/updated usage (values increased)
            const isNewUsage = 
              (usage.prompt_tokens !== undefined && usage.prompt_tokens > currentRequest.promptTokens) ||
              (usage.completion_tokens !== undefined && usage.completion_tokens > currentRequest.completionTokens) ||
              (usage.total_tokens !== undefined && usage.total_tokens > currentRequest.totalTokens);
            
            if (isNewUsage) {
              // Update session totals: subtract old request usage, add new
              this.tokenUsage.promptTokens = this.tokenUsage.promptTokens - currentRequest.promptTokens + (usage.prompt_tokens || 0);
              this.tokenUsage.completionTokens = this.tokenUsage.completionTokens - currentRequest.completionTokens + (usage.completion_tokens || 0);
              this.tokenUsage.totalTokens = this.tokenUsage.totalTokens - currentRequest.totalTokens + (usage.total_tokens || 0);
              
              // Store current request usage
              this.tokenUsage.currentRequest = {
                promptTokens: usage.prompt_tokens || 0,
                completionTokens: usage.completion_tokens || 0,
                totalTokens: usage.total_tokens || 0
              };
            }
          }
        },
        getRemainingTokens() {
          if (!this.contextSize) return null;
          return Math.max(0, this.contextSize - this.tokenUsage.totalTokens);
        },
        getContextUsagePercent() {
          if (!this.contextSize) return null;
          return Math.min(100, (this.tokenUsage.totalTokens / this.contextSize) * 100);
        },
        add(role, content, image, audio) {
          const N = this.history.length - 1;
          // For thinking, reasoning, tool_call, and tool_result messages, always create a new message
          if (role === "thinking" || role === "reasoning" || role === "tool_call" || role === "tool_result") {
            let c = "";
            if (role === "tool_call" || role === "tool_result") {
              // For tool calls and results, try to parse as JSON and format nicely
              try {
                const parsed = typeof content === 'string' ? JSON.parse(content) : content;
                // Format JSON with proper indentation
                const formatted = JSON.stringify(parsed, null, 2);
                c = DOMPurify.sanitize('<pre><code class="language-json">' + formatted + '</code></pre>');
              } catch (e) {
                // If not JSON, treat as markdown
                const lines = content.split("\n");
                lines.forEach((line) => {
                  c += DOMPurify.sanitize(marked.parse(line));
                });
              }
            } else {
              // For thinking and reasoning, format as markdown
              const lines = content.split("\n");
              lines.forEach((line) => {
                c += DOMPurify.sanitize(marked.parse(line));
              });
            }
            // Set expanded state: thinking is expanded by default in non-MCP mode, collapsed in MCP mode
            // Reasoning, tool_call, and tool_result are always collapsed by default
            const isMCPMode = this.mcpMode || false;
            const shouldExpand = (role === "thinking" && !isMCPMode) || false;
            this.history.push({ role, content, html: c, image, audio, expanded: shouldExpand });
            
          }
          // For other messages, merge if same role
          else if (this.history.length && this.history[N].role === role) {
            this.history[N].content += content;
            this.history[N].html = DOMPurify.sanitize(
              marked.parse(this.history[N].content)
            );
            // Merge new images and audio with existing ones
            if (image && image.length > 0) {
              this.history[N].image = [...(this.history[N].image || []), ...image];
            }
            if (audio && audio.length > 0) {
              this.history[N].audio = [...(this.history[N].audio || []), ...audio];
            }
          } else {
            let c = "";
            const lines = content.split("\n");
            lines.forEach((line) => {
              c += DOMPurify.sanitize(marked.parse(line));
            });
            this.history.push({ 
              role, 
              content, 
              html: c, 
              image: image || [], 
              audio: audio || [] 
            });
          }
          // Scroll to bottom consistently for all messages (use #chat as it's the scrollable container)
          setTimeout(() => {
            const chatContainer = document.getElementById('chat');
            if (chatContainer) {
              chatContainer.scrollTo({
                top: chatContainer.scrollHeight,
                behavior: 'smooth'
              });
            }
            // Also scroll thinking box if it's a thinking/reasoning message
            if (role === "thinking" || role === "reasoning") {
              if (typeof window.scrollThinkingBoxToBottom === 'function') {
                window.scrollThinkingBoxToBottom();
              }
            }
          }, 100);
          const parser = new DOMParser();
          const html = parser.parseFromString(
            this.history[this.history.length - 1].html,
            "text/html"
          );
          const code = html.querySelectorAll("pre code");
          if (!code.length) return;
          code.forEach((el) => {
            const language = el.className.split("language-")[1];
            if (this.languages.includes(language)) return;
            const script = document.createElement("script");
            script.src = `https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.8.0/build/languages/${language}.min.js`;
            script.onload = () => {
              // Re-highlight after language script loads
              if (window.hljs) {
                const container = document.getElementById('messages');
                if (container) {
                  container.querySelectorAll('pre code.language-json').forEach(block => {
                    window.hljs.highlightElement(block);
                  });
                }
              }
            };
            document.head.appendChild(script);
            this.languages.push(language);
          });
          // Highlight code blocks immediately if hljs is available
          if (window.hljs) {
            setTimeout(() => {
              const container = document.getElementById('messages');
              if (container) {
                container.querySelectorAll('pre code.language-json').forEach(block => {
                  if (!block.classList.contains('hljs')) {
                    window.hljs.highlightElement(block);
                  }
                });
              }
            }, 100);
          }
        },
        messages() {
          return this.history.map((message) => ({
            role: message.role,
            content: message.content,
            image: message.image,
            audio: message.audio,
          }));
        },
      });
    }

    // Register listener immediately (before Alpine loads)
    document.addEventListener("alpine:init", __initChatStore);
    
    // Also try immediately in case Alpine is already loaded
    if (document.readyState === 'loading') {
      document.addEventListener('DOMContentLoaded', function() {
        if (window.Alpine) __initChatStore();
      });
    } else {
      // DOM already loaded, try immediately
      if (window.Alpine) __initChatStore();
    }
  </script>
  <script defer src="static/chat.js"></script>
  {{ $allGalleryConfigs:=.GalleryConfig }}
  {{ $model:=.Model}}
  <body class="bg-[#101827] text-[#E5E7EB] flex flex-col h-screen" x-data="{ sidebarOpen: true }">
    {{template "views/partials/navbar" .}}

    <!-- Main container with sidebar toggle -->
    <div class="flex flex-1 overflow-hidden relative">
      <!-- Sidebar -->
      <div
        class="sidebar bg-[#1E293B] fixed top-16 bottom-0 left-0 w-64 transform transition-transform duration-300 ease-in-out z-30 border-r border-[#101827] overflow-y-auto"
        :class="sidebarOpen ? 'translate-x-0' : '-translate-x-full'">

        <div class="p-4 flex justify-between items-center border-b border-[#101827]">
          <div class="flex items-center gap-2">
            <h2 class="text-lg font-semibold text-[#E5E7EB]">Chat Settings</h2>
            <a
              href="https://localai.io/features/text-generation/"
              target="_blank"
              class="text-[#94A3B8] hover:text-[#38BDF8] transition-colors"
              title="Documentation">
              <i class="fas fa-book text-sm"></i>
            </a>
          </div>
          <button
            @click="sidebarOpen = false"
            class="text-[#94A3B8] hover:text-[#E5E7EB] focus:outline-none">
            <i class="fa-solid fa-times"></i>
          </button>
        </div>

        <!-- Sidebar content -->
        <div class="p-4 space-y-6">
          <!-- Model selection - Fixed to properly select current model -->
          <div class="space-y-2">
            <label class="text-sm font-medium text-[#94A3B8]">Select Model</label>
            <select
              id="modelSelector"
              class="w-full bg-[#101827] text-[#E5E7EB] border border-[#1E293B] focus:border-[#38BDF8] focus:ring-2 focus:ring-[#38BDF8]/50 rounded-md shadow-sm p-2 appearance-none"
              onchange="window.location = this.value"
            >
              <option value="" disabled class="text-[#94A3B8]">Select a model</option>

              {{ range .ModelsConfig }}
                {{ $cfg := . }}
                {{ range .KnownUsecaseStrings }}
                  {{ if eq . "FLAG_CHAT" }}
                    <option
                      value="chat/{{$cfg.Name}}"
                      {{ if eq $cfg.Name $model }} selected {{end}}
                      class="bg-[#101827] text-[#E5E7EB]"
                    >
                      {{$cfg.Name}}
                    </option>
                  {{ end }}
                {{ end }}
              {{ end }}
              {{ range .ModelsWithoutConfig }}
                <option
                  value="chat/{{.}}"
                  {{ if eq . $model }} selected {{ end }}
                  class="bg-[#101827] text-[#E5E7EB]"
                >
                  {{.}}
                </option>
              {{end}}
            </select>
          </div>

          {{ if $model }}
          {{ $galleryConfig:= index $allGalleryConfigs $model}}
          {{ if $galleryConfig }}
          <!-- Model info -->
          <div class="space-y-2">
            <div class="flex items-center">
              {{ if $galleryConfig.Icon }}<img src="{{$galleryConfig.Icon}}" class="rounded-lg w-8 h-8 mr-2">{{end}}
              <h3 class="text-md font-medium">{{ $model }}</h3>
              <button 
                data-twe-ripple-init 
                data-twe-ripple-color="light" 
                class="ml-2 text-[#94A3B8] hover:text-[#38BDF8] transition-colors" 
                data-modal-target="model-info-modal" 
                data-modal-toggle="model-info-modal"
                title="Model Information">
                <i class="fas fa-info-circle text-sm"></i>
              </button>
              <button
                @click="$store.chat.clear()"
                id="clear"
                title="Clear chat history"
                class="ml-2 text-[#94A3B8] hover:text-[#38BDF8] transition-colors">
                <i class="fa-solid fa-trash-can text-sm"></i>
              </button>
            </div>
          </div>
          {{ end }}
          {{ end }}

          <div x-data="{ showPromptForm: false }" class="space-y-3">
              <!-- Token Usage Statistics -->
              <div class="bg-[#1E293B] border border-[#38BDF8]/20 rounded-lg p-3 space-y-2">
                <div class="flex items-center justify-between mb-2">
                  <h4 class="text-sm font-semibold text-[#E5E7EB] flex items-center">
                    <i class="fas fa-chart-line mr-2 text-[#38BDF8]"></i>
                    Token Usage
                  </h4>
                </div>
                <div class="space-y-1.5 text-xs">
                  <div class="flex justify-between text-[#94A3B8]">
                    <span>Prompt:</span>
                    <span class="text-[#E5E7EB] font-medium" x-text="new Intl.NumberFormat().format($store.chat.tokenUsage.promptTokens)"></span>
                  </div>
                  <div class="flex justify-between text-[#94A3B8]">
                    <span>Completion:</span>
                    <span class="text-[#E5E7EB] font-medium" x-text="new Intl.NumberFormat().format($store.chat.tokenUsage.completionTokens)"></span>
                  </div>
                  <div class="flex justify-between text-[#94A3B8] border-t border-[#101827] pt-1.5">
                    <span class="font-semibold text-[#38BDF8]">Total:</span>
                    <span class="text-[#E5E7EB] font-bold" x-text="new Intl.NumberFormat().format($store.chat.tokenUsage.totalTokens)"></span>
                  </div>
                </div>
              </div>

              <!-- Context Size Indicator -->
              <template x-if="$store.chat.contextSize && $store.chat.contextSize > 0">
                <div class="bg-[#1E293B] border border-[#38BDF8]/20 rounded-lg p-3 space-y-2">
                  <div class="flex items-center justify-between mb-2">
                    <h4 class="text-sm font-semibold text-[#E5E7EB] flex items-center">
                      <i class="fas fa-database mr-2 text-[#38BDF8]"></i>
                      Context Window
                    </h4>
                  </div>
                  <div class="space-y-2">
                    <div class="flex justify-between text-xs text-[#94A3B8] mb-1">
                      <span>Used / Available</span>
                      <span class="text-[#E5E7EB] font-medium">
                        <span x-text="new Intl.NumberFormat().format($store.chat.tokenUsage.totalTokens)"></span>
                        / 
                        <span x-text="new Intl.NumberFormat().format($store.chat.contextSize)"></span>
                      </span>
                    </div>
                    <div class="w-full bg-[#101827] rounded-full h-2 overflow-hidden border border-[#1E293B]">
                      <div class="h-full rounded-full transition-all duration-300 ease-out"
                           :class="{
                             'bg-gradient-to-r from-[#38BDF8] to-[#8B5CF6]': $store.chat.getContextUsagePercent() < 80,
                             'bg-gradient-to-r from-yellow-500 to-orange-500': $store.chat.getContextUsagePercent() >= 80 && $store.chat.getContextUsagePercent() < 95,
                             'bg-gradient-to-r from-red-500 to-red-600': $store.chat.getContextUsagePercent() >= 95
                           }"
                           :style="'width: ' + Math.min(100, $store.chat.getContextUsagePercent()) + '%'">
                      </div>
                    </div>
                    <div class="flex justify-between text-xs">
                      <span class="text-[#94A3B8]">
                        Remaining: 
                        <span class="text-[#E5E7EB] font-medium" x-text="new Intl.NumberFormat().format($store.chat.getRemainingTokens())"></span>
                      </span>
                      <span class="text-[#94A3B8]">
                        <span x-text="Math.round($store.chat.getContextUsagePercent())"></span>%
                      </span>
                    </div>
                    <div x-show="$store.chat.getContextUsagePercent() >= 80" class="mt-2 p-2 bg-yellow-500/10 border border-yellow-500/30 rounded text-yellow-300 text-xs">
                      <i class="fas fa-exclamation-triangle mr-1"></i>
                      <span x-show="$store.chat.getContextUsagePercent() >= 95">Context window nearly full!</span>
                      <span x-show="$store.chat.getContextUsagePercent() >= 80 && $store.chat.getContextUsagePercent() < 95">Approaching context limit</span>
                    </div>
                  </div>
                </div>
              </template>

              {{ if $model }}
              {{ $galleryConfig:= index $allGalleryConfigs $model}}
              {{ if $galleryConfig }}
              {{ $modelConfig := "" }}
              {{ range .ModelsConfig }}
                {{ if eq .Name $model }}
                  {{ $modelConfig = . }}
                {{ end }}
              {{ end }}
              {{ if and $modelConfig (or (ne $modelConfig.MCP.Servers "") (ne $modelConfig.MCP.Stdio "")) }}
              <!-- MCP Toggle -->
              <div class="flex items-center justify-between px-3 py-2 text-sm rounded text-[#E5E7EB] bg-[#1E293B] border border-[#38BDF8]/20">
                <span><i class="fa-solid fa-plug mr-2 text-[#38BDF8]"></i> Agentic MCP Mode</span>
                <label class="relative inline-flex items-center cursor-pointer">
                  <input type="checkbox" id="mcp-toggle" class="sr-only peer" x-model="$store.chat.mcpMode">
                  <div class="w-11 h-6 bg-[#101827] peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-[#38BDF8]/30 rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-[#1E293B] after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-[#38BDF8]"></div>
                </label>
              </div>

              <!-- MCP Mode Notification -->
              <div x-show="$store.chat.mcpMode" class="p-3 bg-[#38BDF8]/10 border border-[#38BDF8]/30 rounded text-[#94A3B8] text-xs">
                <div class="flex items-start space-x-2">
                  <i class="fa-solid fa-info-circle text-[#38BDF8] mt-0.5"></i>
                  <div>
                    <p class="font-medium text-[#E5E7EB] mb-1">Non-streaming Mode Active</p>
                    <p class="text-[#94A3B8]">Responses will be processed in full before display. This may take significantly longer (up to 5 minutes), especially on CPU-only systems.</p>
                  </div>
                </div>
              </div>
              {{ end }}
              {{ end }}
              {{ end }}

              <button
                @click="showPromptForm = !showPromptForm"
                class="w-full flex items-center justify-between px-3 py-2 text-sm rounded text-[#E5E7EB] bg-[#1E293B] hover:bg-[#1E293B]/80 border border-[#38BDF8]/20 hover:border-[#38BDF8]/40 transition-colors glow-on-hover"
              >
                <span><i class="fa-solid fa-message mr-2 text-[#38BDF8]"></i> System Prompt</span>
                <i :class="showPromptForm ? 'fa-chevron-up' : 'fa-chevron-down'" class="fa-solid"></i>
              </button>

              <div x-show="showPromptForm" x-data="{
                showToast: false,
                previousPrompt: $store.chat.systemPrompt,
                isUpdated() {
                  if (this.previousPrompt !== $store.chat.systemPrompt) {
                    this.showToast = true;
                    this.previousPrompt = $store.chat.systemPrompt;
                    setTimeout(() => {this.showToast = false;}, 2000);
                  }
                } 
              }" class="p-3 bg-[#1E293B] border border-[#38BDF8]/20 rounded-lg">
                <form id="system_prompt" @submit.prevent="isUpdated" class="flex flex-col space-y-2">
                  <textarea
                    type="text"
                    id="systemPrompt"
                    name="systemPrompt"
                    class="bg-[#101827] text-[#E5E7EB] border border-[#1E293B] focus:border-[#38BDF8] focus:ring focus:ring-[#38BDF8] focus:ring-opacity-50 rounded-md shadow-sm p-2 appearance-none min-h-24 placeholder-[#94A3B8]"
                    placeholder="System prompt"
                    x-model.lazy="$store.chat.systemPrompt"
                  ></textarea>
                  <div
                    x-show="showToast"
                    x-transition
                    class="mb-2 text-green-400 px-4 py-2 text-sm text-center bg-green-500/10 border border-green-500/30 rounded"
                  >
                    System prompt updated!
                  </div>
                  <button
                    type="submit"
                    class="px-3 py-2 text-sm rounded text-[#101827] bg-[#38BDF8] hover:bg-[#38BDF8]/90 transition-colors font-medium"
                  >
                    Save System Prompt
                  </button>
                </form>
              </div>
          </div>
        </div>
      </div>

      <!-- Main chat container (shifts with sidebar) -->
      <div
        class="flex-1 flex flex-col transition-all duration-300 ease-in-out"
        :class="sidebarOpen ? 'ml-64' : 'ml-0'">

        <!-- Chat header with toggle button -->
        <div class="border-b border-[#1E293B] p-4 flex items-center justify-between">
          <div class="flex items-center">
            <!-- Sidebar toggle button moved to be the first element in the header and with clear styling -->
            <button
              @click="sidebarOpen = !sidebarOpen"
              class="mr-4 text-[#94A3B8] hover:text-[#E5E7EB] focus:outline-none bg-[#1E293B] hover:bg-[#1E293B]/80 p-2 rounded transition-colors"
              style="min-width: 36px;"
              title="Toggle settings">
              <i class="fa-solid" :class="sidebarOpen ? 'fa-times' : 'fa-bars'"></i>
            </button>

            <div class="flex items-center">
              <i class="fa-solid fa-comments mr-2 text-[#38BDF8]"></i>
              {{ if $model }}
              {{ $galleryConfig:= index $allGalleryConfigs $model}}
              {{ if $galleryConfig }}
              {{ if $galleryConfig.Icon }}<img src="{{$galleryConfig.Icon}}" class="rounded-lg w-8 h-8 mr-2">{{end}}
              {{ end }}
              {{ end }}
              <h1 class="text-lg font-semibold text-[#E5E7EB]">
                Chat {{ if .Model }} with {{.Model}} {{ end }}
              </h1>
              <!-- Loading indicator next to model name -->
              <div id="header-loading-indicator" class="ml-3 text-[#38BDF8]" style="display: none;">
                <i class="fas fa-spinner fa-spin text-sm"></i>
              </div>
            </div>
          </div>

        </div>

        <!-- Chat messages area -->
        <div class="flex-1 p-4 overflow-auto" id="chat" x-data="{history: $store.chat.history}">
          <p id="usage" x-show="history.length === 0" class="text-[#94A3B8]">
            Start chatting with the AI by typing a prompt in the input field below and pressing Enter.<br>
            <ul class="list-disc list-inside mt-2 space-y-1">
              <li>For models that support images, you can upload an image by clicking the <i class="fa-solid fa-image text-[#38BDF8]"></i> icon.</li>
              <li>For models that support audio, you can upload an audio file by clicking the <i class="fa-solid fa-microphone text-[#38BDF8]"></i> icon.</li>
              <li>To send a text, markdown or PDF file, click the <i class="fa-solid fa-file text-[#38BDF8]"></i> icon.</li>
            </ul>
          </p>
          <div id="messages" class="max-w-3xl mx-auto space-y-2">
            <template x-for="(message, index) in history" :key="index">
              <div>
                <!-- Reasoning/Thinking messages appear first (before assistant) - collapsible in MCP mode -->
                <template x-if="message.role === 'reasoning' || message.role === 'thinking'">
                  <div class="flex items-start space-x-2 mb-1">
                    <div class="flex flex-col flex-1">
                      <div class="p-2 flex-1 rounded-lg bg-[#38BDF8]/10 text-[#94A3B8] border border-[#38BDF8]/30">
                        <button 
                          @click="message.expanded = !message.expanded"
                          class="w-full flex items-center justify-between text-left hover:bg-[#38BDF8]/20 rounded p-2 transition-colors"
                        >
                          <div class="flex items-center space-x-2">
                            <i :class="message.role === 'thinking' ? 'fa-solid fa-brain' : 'fa-solid fa-lightbulb'" class="text-[#38BDF8]"></i>
                            <span class="text-xs font-semibold text-[#38BDF8]" x-text="message.role === 'thinking' ? 'Thinking' : 'Reasoning'"></span>
                            <span class="text-xs text-[#94A3B8]" x-show="message.content && message.content.length > 0" x-text="'(' + Math.ceil(message.content.length / 100) + ' lines)'"></span>
                          </div>
                          <i 
                            class="fa-solid text-[#38BDF8] transition-transform text-xs"
                            :class="message.expanded ? 'fa-chevron-up' : 'fa-chevron-down'"
                          ></i>
                        </button>
                        <div 
                          x-show="message.expanded"
                          x-transition
                          class="mt-2 pt-2 border-t border-[#38BDF8]/20"
                        >
                          <div 
                            class="text-[#E5E7EB] text-sm max-h-96 overflow-auto" 
                            x-html="message.html"
                            data-thinking-box
                            x-effect="if (message.expanded && message.html) { setTimeout(() => { if ($el.scrollHeight > $el.clientHeight) { $el.scrollTo({ top: $el.scrollHeight, behavior: 'smooth' }); } }, 50); }"
                          ></div>
                        </div>
                      </div>
                    </div>
                  </div>
                </template>
                
                <!-- Tool calls (collapsible) -->
                <template x-if="message.role === 'tool_call'">
                  <div class="flex items-start space-x-2 mb-1">
                    <div class="flex flex-col flex-1">
                      <div class="p-2 flex-1 rounded-lg bg-[#8B5CF6]/10 text-[#94A3B8] border border-[#8B5CF6]/30">
                        <button 
                          @click="message.expanded = !message.expanded"
                          class="w-full flex items-center justify-between text-left hover:bg-[#8B5CF6]/20 rounded p-2 transition-colors"
                        >
                          <div class="flex items-center space-x-2">
                            <i class="fa-solid fa-wrench text-[#8B5CF6]"></i>
                            <span class="text-xs font-semibold text-[#8B5CF6]">Tool Call</span>
                            <span class="text-xs text-[#94A3B8]" x-text="getToolName(message.content)"></span>
                          </div>
                          <i 
                            class="fa-solid text-[#8B5CF6] transition-transform text-xs"
                            :class="message.expanded ? 'fa-chevron-up' : 'fa-chevron-down'"
                          ></i>
                        </button>
                        <div 
                          x-show="message.expanded"
                          x-transition
                          class="mt-2 pt-2 border-t border-[#8B5CF6]/20"
                        >
                          <div class="text-[#E5E7EB] text-xs max-h-96 overflow-auto overflow-x-auto tool-call-content" 
                               x-html="message.html"
                               x-effect="if (message.expanded && window.hljs) { setTimeout(() => { $el.querySelectorAll('pre code.language-json').forEach(block => { if (!block.classList.contains('hljs')) window.hljs.highlightElement(block); }); }, 50); }"></div>
                        </div>
                      </div>
                    </div>
                  </div>
                </template>
                
                <!-- Tool results (collapsible) -->
                <template x-if="message.role === 'tool_result'">
                  <div class="flex items-start space-x-2 mb-1">
                    <div class="flex flex-col flex-1">
                      <div class="p-2 flex-1 rounded-lg bg-[#10B981]/10 text-[#94A3B8] border border-[#10B981]/30">
                        <button 
                          @click="message.expanded = !message.expanded"
                          class="w-full flex items-center justify-between text-left hover:bg-[#10B981]/20 rounded p-2 transition-colors"
                        >
                          <div class="flex items-center space-x-2">
                            <i class="fa-solid fa-check-circle text-[#10B981]"></i>
                            <span class="text-xs font-semibold text-[#10B981]">Tool Result</span>
                            <span class="text-xs text-[#94A3B8]" x-text="getToolName(message.content) || 'Success'"></span>
                          </div>
                          <i 
                            class="fa-solid text-[#10B981] transition-transform text-xs"
                            :class="message.expanded ? 'fa-chevron-up' : 'fa-chevron-down'"
                          ></i>
                        </button>
                        <div 
                          x-show="message.expanded"
                          x-transition
                          class="mt-2 pt-2 border-t border-[#10B981]/20"
                        >
                          <div class="text-[#E5E7EB] text-xs max-h-96 overflow-auto overflow-x-auto tool-result-content" 
                               x-html="formatToolResult(message.content)"
                               x-effect="if (message.expanded && window.hljs) { setTimeout(() => { $el.querySelectorAll('pre code.language-json').forEach(block => { if (!block.classList.contains('hljs')) window.hljs.highlightElement(block); }); }, 50); }"></div>
                        </div>
                      </div>
                    </div>
                  </div>
                </template>
                
                <!-- User and Assistant messages -->
                <div :class="message.role === 'user' ? 'flex items-start space-x-2 justify-end' : 'flex items-start space-x-2'">
                {{ if .Model }}
                {{ $galleryConfig:= index $allGalleryConfigs .Model}}
                <template x-if="message.role === 'user'">
                  <div class="flex items-center space-x-2">
                    <div class="flex flex-col flex-1 items-end">
                      <span class="text-xs font-semibold text-[#94A3B8] mb-1">You</span>
                      <div class="p-3 flex-1 rounded-lg bg-gradient-to-br from-[#1E293B] to-[#101827] text-[#E5E7EB] border border-[#38BDF8]/20 shadow-lg" x-html="message.html"></div>
                      <template x-if="message.image && message.image.length > 0">
                        <div class="mt-2 space-y-2">
                          <template x-for="(img, index) in message.image" :key="index">
                            <img :src="img" :alt="'Image ' + (index + 1)" class="rounded-lg max-w-xs">
                          </template>
                        </div>
                      </template>
                      <template x-if="message.audio && message.audio.length > 0">
                        <div class="mt-2 space-y-2">
                          <template x-for="(audio, index) in message.audio" :key="index">
                            <audio controls class="w-full">
                              <source :src="audio" type="audio/*">
                              Your browser does not support the audio element.
                            </audio>
                          </template>
                        </div>
                      </template>
                    </div>
                  </div>
                </template>
                <template x-if="message.role != 'user' && message.role != 'thinking' && message.role != 'reasoning' && message.role != 'tool_call' && message.role != 'tool_result'">
                  <div class="flex items-center space-x-2">
                    {{ if $galleryConfig }}
                    {{ if $galleryConfig.Icon }}<img src="{{$galleryConfig.Icon}}" class="rounded-lg mt-2 max-w-8 max-h-8 border border-[#38BDF8]/20">{{end}}
                    {{ end }}
                    <div class="flex flex-col flex-1">
                      <span class="text-xs font-semibold text-[#94A3B8] mb-1">{{if .Model}}{{.Model}}{{else}}Assistant{{end}}</span>
                      <div class="flex-1 text-[#E5E7EB] flex items-center space-x-2">
                        <div class="p-3 rounded-lg bg-gradient-to-br from-[#1E293B] to-[#101827] border border-[#8B5CF6]/20 shadow-lg" x-html="message.html"></div>
                        <button @click="copyToClipboard(message.html)" title="Copy to clipboard" class="text-[#94A3B8] hover:text-[#38BDF8] transition-colors p-1">
                          <i class="fa-solid fa-copy"></i>
                        </button>
                      </div>
                      <template x-if="message.image && message.image.length > 0">
                        <div class="mt-2 space-y-2">
                          <template x-for="(img, index) in message.image" :key="index">
                            <img :src="img" :alt="'Image ' + (index + 1)" class="rounded-lg max-w-xs">
                          </template>
                        </div>
                      </template>
                      <template x-if="message.audio && message.audio.length > 0">
                        <div class="mt-2 space-y-2">
                          <template x-for="(audio, index) in message.audio" :key="index">
                            <audio controls class="w-full">
                              <source :src="audio" type="audio/*">
                              Your browser does not support the audio element.
                            </audio>
                          </template>
                        </div>
                      </template>
                    </div>
                  </div>
                </template>
                {{ else }}
                <i
                  class="fa-solid h-8 w-8"
                  :class="message.role === 'user' ? 'fa-user text-[#38BDF8]' : 'fa-robot text-[#8B5CF6]'"
                ></i>
                {{ end }}
                </div>
              </div>
            </template>
          </div>
        </div>


          <!-- Chat Input -->
          <div class="p-4 border-t border-[#1E293B]" x-data="{ inputValue: '', shiftPressed: false, attachedFiles: [] }">
            <form id="prompt" action="chat/{{.Model}}" method="get" @submit.prevent="submitPrompt" class="max-w-3xl mx-auto">
              <!-- Attachment Tags - Show above input when files are attached -->
              <div x-show="attachedFiles.length > 0" class="mb-3 flex flex-wrap gap-2 items-center">
                <template x-for="(file, index) in attachedFiles" :key="index">
                  <div class="inline-flex items-center gap-2 px-3 py-1.5 rounded-lg text-sm bg-[#38BDF8]/20 border border-[#38BDF8]/40 text-[#E5E7EB]">
                    <i :class="file.type === 'image' ? 'fa-solid fa-image' : file.type === 'audio' ? 'fa-solid fa-microphone' : 'fa-solid fa-file'" class="text-[#38BDF8]"></i>
                    <span x-text="file.name" class="max-w-[200px] truncate"></span>
                    <button 
                      type="button"
                      @click="attachedFiles.splice(index, 1); removeFileFromInput(file.type, file.name)"
                      class="ml-1 text-[#94A3B8] hover:text-[#E5E7EB] transition-colors"
                      title="Remove attachment"
                    >
                      <i class="fa-solid fa-times text-xs"></i>
                    </button>
                  </div>
                </template>
              </div>

              <!-- Token Usage and Context Window - Compact above input -->
              <div class="mb-3 flex items-center justify-between gap-4 text-xs">
                <!-- Token Usage -->
                <div class="flex items-center gap-3 text-[#94A3B8]">
                  <div class="flex items-center gap-1">
                    <i class="fas fa-chart-line text-[#38BDF8]"></i>
                    <span>Prompt:</span>
                    <span class="text-[#E5E7EB] font-medium" x-text="new Intl.NumberFormat().format($store.chat.tokenUsage.promptTokens)"></span>
                  </div>
                  <div class="flex items-center gap-1">
                    <span>Completion:</span>
                    <span class="text-[#E5E7EB] font-medium" x-text="new Intl.NumberFormat().format($store.chat.tokenUsage.completionTokens)"></span>
                  </div>
                  <div class="flex items-center gap-1 border-l border-[#1E293B] pl-3">
                    <span class="text-[#38BDF8] font-semibold">Total:</span>
                    <span class="text-[#E5E7EB] font-bold" x-text="new Intl.NumberFormat().format($store.chat.tokenUsage.totalTokens)"></span>
                  </div>
                  <!-- Tokens per second display -->
                  <div id="tokens-per-second-container" class="flex items-center gap-1 border-l border-[#1E293B] pl-3">
                    <i class="fas fa-tachometer-alt text-[#38BDF8]"></i>
                    <span id="tokens-per-second" class="text-[#E5E7EB] font-medium">-</span>
                  </div>
                </div>

                <!-- Context Window -->
                <template x-if="$store.chat.contextSize && $store.chat.contextSize > 0">
                  <div class="flex items-center gap-2 text-[#94A3B8]">
                    <i class="fas fa-database text-[#38BDF8]"></i>
                    <span>
                      <span class="text-[#E5E7EB] font-medium" x-text="new Intl.NumberFormat().format($store.chat.tokenUsage.totalTokens)"></span>
                      / 
                      <span class="text-[#E5E7EB] font-medium" x-text="new Intl.NumberFormat().format($store.chat.contextSize)"></span>
                    </span>
                    <div class="w-16 bg-[#101827] rounded-full h-1.5 overflow-hidden border border-[#1E293B]">
                      <div class="h-full rounded-full transition-all duration-300 ease-out"
                           :class="{
                             'bg-gradient-to-r from-[#38BDF8] to-[#8B5CF6]': $store.chat.getContextUsagePercent() < 80,
                             'bg-gradient-to-r from-yellow-500 to-orange-500': $store.chat.getContextUsagePercent() >= 80 && $store.chat.getContextUsagePercent() < 95,
                             'bg-gradient-to-r from-red-500 to-red-600': $store.chat.getContextUsagePercent() >= 95
                           }"
                           :style="'width: ' + Math.min(100, $store.chat.getContextUsagePercent()) + '%'">
                      </div>
                    </div>
                    <span class="text-[#94A3B8]" x-text="Math.round($store.chat.getContextUsagePercent()) + '%'"></span>
                    <span x-show="$store.chat.getContextUsagePercent() >= 80" class="text-yellow-400">
                      <i class="fas fa-exclamation-triangle"></i>
                    </span>
                  </div>
                </template>
              </div>

              <div class="relative w-full bg-[#1E293B] border border-[#38BDF8]/20 rounded-xl shadow-lg focus-within:ring-2 focus-within:ring-[#38BDF8]/50 focus-within:border-[#38BDF8] transition-all duration-200">
                <textarea
                  id="input"
                  name="input"
                  x-model="inputValue"
                  placeholder="Send a message..."
                  class="p-3 pr-16 w-full bg-[#1E293B] text-[#E5E7EB] placeholder-[#94A3B8] focus:outline-none resize-none border-0 rounded-xl transition-colors duration-200"
                  required
                  @keydown.shift="shiftPressed = true"
                  @keyup.shift="shiftPressed = false"
                  @keydown.enter.prevent="if (!shiftPressed) { submitPrompt($event); }"
                  rows="2"
                ></textarea>
                <button
                  type="button"
                  onclick="document.getElementById('input_image').click()"
                  class="fa-solid fa-image text-[#94A3B8] absolute right-12 top-3 text-base p-1.5 hover:text-[#38BDF8] transition-colors duration-200"
                  title="Attach images"
                ></button>
                <button
                  type="button"
                  onclick="document.getElementById('input_audio').click()"
                  class="fa-solid fa-microphone text-[#94A3B8] absolute right-20 top-3 text-base p-1.5 hover:text-[#38BDF8] transition-colors duration-200"
                  title="Attach an audio file"
                ></button>
                <button
                  type="button"
                  onclick="document.getElementById('input_file').click()"
                  class="fa-solid fa-file text-[#94A3B8] absolute right-28 top-3 text-base p-1.5 hover:text-[#38BDF8] transition-colors duration-200"
                  title="Upload text, markdown or PDF file"
                ></button>

                <!-- Send button and stop button in the same position -->
                <div class="absolute right-3 top-3 flex items-center">
                  <!-- Stop button (hidden by default, shown when request is in progress) -->
                  <button
                    id="stop-button"
                    type="button"
                    onclick="stopRequest()"
                    class="text-lg p-2 text-red-400 hover:text-red-500 transition-colors duration-200"
                    style="display: none;"
                    title="Stop request"
                  >
                    <i class="fa-solid fa-stop"></i>
                  </button>

                  <!-- Send button -->
                  <button
                    id="send-button"
                    type="submit"
                    class="text-lg p-2 text-[#94A3B8] hover:text-[#38BDF8] transition-colors duration-200"
                    title="Send message (Enter)"
                  >
                    <i class="fa-solid fa-paper-plane"></i>
                  </button>
                </div>
              </div>
            </form>
            <input id="chat-model" type="hidden" value="{{.Model}}" {{ if .ContextSize }}data-context-size="{{.ContextSize}}"{{ end }}>
            <input
              id="input_image"
              type="file"
              multiple
              accept="image/*"
              style="display: none;"
              @change="handleFileSelection($event, 'image')"
            />
            <input
              id="input_audio"
              type="file"
              multiple
              accept="audio/*"
              style="display: none;"
              @change="handleFileSelection($event, 'audio')"
            />
            <input
              id="input_file"
              type="file"
              multiple
              accept=".txt,.md,.pdf"
              style="display: none;"
              @change="handleFileSelection($event, 'file')"
            />
          </div>
          </form>
        </div>
      </div>
    </div>

    <!-- Modal moved outside of sidebar to appear in center of page -->
    {{ if $model }}
    {{ $galleryConfig:= index $allGalleryConfigs $model}}
    {{ if $galleryConfig }}
    <div id="model-info-modal" tabindex="-1" aria-hidden="true" class="hidden overflow-y-auto overflow-x-hidden fixed top-0 right-0 left-0 z-50 flex justify-center items-center w-full md:inset-0 h-[calc(100%-1rem)] max-h-full">
      <div class="relative p-4 w-full max-w-2xl max-h-full">
        <div class="relative p-4 w-full max-w-2xl max-h-full bg-white rounded-lg shadow dark:bg-gray-700">
          <!-- Header -->
          <div class="flex items-center justify-between p-4 md:p-5 border-b rounded-t dark:border-gray-600">
            <h3 class="text-xl font-semibold text-gray-900 dark:text-white">{{ $model }}</h3>
            <button class="text-gray-400 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm w-8 h-8 ms-auto inline-flex justify-center items-center dark:hover:bg-gray-600 dark:hover:text-white" data-modal-hide="model-info-modal">
              <svg class="w-3 h-3" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 14 14">
                <path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m1 1 6 6m0 0 6 6M7 7l6-6M7 7l-6 6"/>
              </svg>
              <span class="sr-only">Close modal</span>
            </button>
          </div>

          <!-- Body -->
          <div class="p-4 md:p-5 space-y-4">
            <div class="flex justify-center items-center">
              {{ if $galleryConfig.Icon }}<img class="lazy rounded-t-lg max-h-48 max-w-96 object-cover mt-3 entered loaded" src="{{$galleryConfig.Icon}}" loading="lazy"/>{{end}}
            </div>
            <div id="model-info-description" class="text-base leading-relaxed text-gray-500 dark:text-gray-400 break-words max-w-full">{{ $galleryConfig.Description }}</div>
            <hr>
            <p class="text-sm font-semibold text-gray-900 dark:text-white">Links</p>
            <ul>
              {{range $galleryConfig.URLs}}
              <li><a href="{{ . }}" target="_blank">{{ . }}</a></li>
              {{end}}
            </ul>
          </div>

          <!-- Footer -->
          <div class="flex items-center p-4 md:p-5 border-t border-gray-200 rounded-b dark:border-gray-600">
            <button data-modal-hide="model-info-modal" class="py-2.5 px-5 ms-3 text-sm font-medium text-gray-900 focus:outline-none bg-white rounded-lg border border-gray-200 hover:bg-gray-100 hover:text-blue-700 focus:z-10 focus:ring-4 focus:ring-gray-100 dark:focus:ring-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:border-gray-600 dark:hover:text-white dark:hover:bg-gray-700">
              Close
            </button>
          </div>
        </div>
      </div>
    </div>
    {{ end }}
    {{ end }}

    <!-- Alpine store initialization and utilities -->
    <script>
      document.addEventListener("alpine:init", () => {
        window.copyToClipboard = (content) => {
          const tempElement = document.createElement('div');
          tempElement.innerHTML = content;
          const text = tempElement.textContent || tempElement.innerText;

          navigator.clipboard.writeText(text).then(() => {
            alert('Copied to clipboard!');
          }).catch(err => {
            console.error('Failed to copy: ', err);
          });
        };

        // Format tool result for better display
        window.formatToolResult = (content) => {
          if (!content) return '';
          try {
            // Try to parse as JSON
            const parsed = JSON.parse(content);
            
            // If it has a 'result' field, try to parse that too
            if (parsed.result && typeof parsed.result === 'string') {
              try {
                const resultParsed = JSON.parse(parsed.result);
                parsed.result = resultParsed;
              } catch (e) {
                // Keep as string if not JSON
              }
            }
            
            // Format the JSON nicely
            const formatted = JSON.stringify(parsed, null, 2);
            return DOMPurify.sanitize('<pre class="bg-[#101827] p-3 rounded border border-[#10B981]/20 overflow-x-auto"><code class="language-json">' + formatted + '</code></pre>');
          } catch (e) {
            // If not JSON, try to format as markdown or plain text
            try {
              // Check if it's a markdown code block
              if (content.includes('```')) {
                return DOMPurify.sanitize(marked.parse(content));
              }
              // Otherwise, try to parse as JSON one more time with error handling
              const lines = content.split('\n');
              let jsonStart = -1;
              let jsonEnd = -1;
              for (let i = 0; i < lines.length; i++) {
                if (lines[i].trim().startsWith('{') || lines[i].trim().startsWith('[')) {
                  jsonStart = i;
                  break;
                }
              }
              if (jsonStart >= 0) {
                for (let i = lines.length - 1; i >= jsonStart; i--) {
                  if (lines[i].trim().endsWith('}') || lines[i].trim().endsWith(']')) {
                    jsonEnd = i;
                    break;
                  }
                }
                if (jsonEnd >= jsonStart) {
                  const jsonStr = lines.slice(jsonStart, jsonEnd + 1).join('\n');
                  try {
                    const parsed = JSON.parse(jsonStr);
                    const formatted = JSON.stringify(parsed, null, 2);
                    return DOMPurify.sanitize('<pre class="bg-[#101827] p-3 rounded border border-[#10B981]/20 overflow-x-auto"><code class="language-json">' + formatted + '</code></pre>');
                  } catch (e2) {
                    // Fall through to markdown
                  }
                }
              }
              // Fall back to markdown
              return DOMPurify.sanitize(marked.parse(content));
            } catch (e2) {
              // Last resort: plain text
              return DOMPurify.sanitize('<pre class="bg-[#101827] p-3 rounded border border-[#10B981]/20 overflow-x-auto text-xs">' + content.replace(/</g, '&lt;').replace(/>/g, '&gt;') + '</pre>');
            }
          }
        };

        // Get tool name from content
        window.getToolName = (content) => {
          if (!content || typeof content !== 'string') return '';
          try {
            const parsed = JSON.parse(content);
            return parsed.name || '';
          } catch (e) {
            // Try to extract name from string
            const nameMatch = content.match(/"name"\s*:\s*"([^"]+)"/);
            return nameMatch ? nameMatch[1] : '';
          }
        };
      });

      // Context size is now initialized in the Alpine store initialization above

      // Process markdown in model info modal when it opens
      function initMarkdownProcessing() {
        // Wait for marked and DOMPurify to be available
        if (typeof marked === 'undefined' || typeof DOMPurify === 'undefined') {
          setTimeout(initMarkdownProcessing, 100);
          return;
        }

        const modalElement = document.getElementById('model-info-modal');
        const descriptionElement = document.getElementById('model-info-description');
        
        if (!modalElement || !descriptionElement) {
          return;
        }

        // Store original text in data attribute if not already stored
        let originalText = descriptionElement.dataset.originalText;
        if (!originalText) {
          originalText = descriptionElement.textContent || descriptionElement.innerText;
          descriptionElement.dataset.originalText = originalText;
        }

        // Process markdown function
        const processMarkdown = () => {
          if (!descriptionElement || !originalText) return;
          
          try {
            // Check if already processed (has HTML tags that look like markdown output)
            const currentContent = descriptionElement.innerHTML.trim();
            if (currentContent.startsWith('<') && (currentContent.includes('<p>') || currentContent.includes('<h') || currentContent.includes('<ul>') || currentContent.includes('<ol>'))) {
              return; // Already processed
            }
            
            // Use stored original text
            const textToProcess = descriptionElement.dataset.originalText || originalText;
            if (textToProcess && textToProcess.trim()) {
              const html = marked.parse(textToProcess);
              descriptionElement.innerHTML = DOMPurify.sanitize(html);
            }
          } catch (error) {
            console.error('Error rendering markdown:', error);
          }
        };

        // Process immediately if modal is already visible
        if (!modalElement.classList.contains('hidden')) {
          processMarkdown();
        }

        // Listen for modal show events - check both aria-hidden and class changes
        const observer = new MutationObserver((mutations) => {
          mutations.forEach((mutation) => {
            if (mutation.type === 'attributes') {
              const isHidden = modalElement.classList.contains('hidden') || 
                              modalElement.getAttribute('aria-hidden') === 'true';
              if (!isHidden) {
                // Modal is now visible, process markdown
                setTimeout(processMarkdown, 150);
              }
            }
          });
        });

        observer.observe(modalElement, {
          attributes: true,
          attributeFilter: ['aria-hidden', 'class'],
          childList: false,
          subtree: false
        });

        // Also listen for click events on modal toggle buttons
        document.querySelectorAll('[data-modal-toggle="model-info-modal"]').forEach(button => {
          button.addEventListener('click', () => {
            setTimeout(processMarkdown, 300);
          });
        });

        // Process on initial load if libraries are ready
        setTimeout(processMarkdown, 200);
      }

      // Start initialization
      if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', initMarkdownProcessing);
      } else {
        initMarkdownProcessing();
      }
    </script>

    <style>
    /* Markdown content overflow handling */
    #model-info-description {
        word-wrap: break-word;
        overflow-wrap: anywhere;
        max-width: 100%;
    }

    #model-info-description pre {
        overflow-x: auto;
        max-width: 100%;
        white-space: pre-wrap;
        word-wrap: break-word;
    }

    #model-info-description code {
        word-wrap: break-word;
        overflow-wrap: break-word;
    }

    #model-info-description pre code {
        white-space: pre;
        overflow-x: auto;
        display: block;
    }

    #model-info-description table {
        max-width: 100%;
        overflow-x: auto;
        display: block;
    }

    #model-info-description img {
        max-width: 100%;
        height: auto;
    }

    /* Prevent JSON overflow in tool calls and results */
    .tool-call-content pre,
    .tool-result-content pre {
        overflow-x: auto;
        overflow-y: auto;
        max-width: 100%;
        word-wrap: break-word;
        white-space: pre-wrap;
        background: #101827 !important;
        border: 1px solid #1E293B;
        border-radius: 6px;
        padding: 12px;
        margin: 0;
    }

    .tool-call-content code,
    .tool-result-content code {
        word-wrap: break-word;
        white-space: pre-wrap;
        overflow-wrap: break-word;
        background: transparent !important;
        color: #E5E7EB;
        font-family: 'ui-monospace', 'Monaco', 'Consolas', monospace;
        font-size: 0.875rem;
        line-height: 1.5;
    }

    /* Dark theme syntax highlighting for JSON */
    .tool-call-content .hljs,
    .tool-result-content .hljs {
        background: #101827 !important;
        color: #E5E7EB !important;
    }

    .tool-call-content .hljs-keyword,
    .tool-result-content .hljs-keyword {
        color: #8B5CF6 !important;
        font-weight: 600;
    }

    .tool-call-content .hljs-string,
    .tool-result-content .hljs-string {
        color: #10B981 !important;
    }

    .tool-call-content .hljs-number,
    .tool-result-content .hljs-number {
        color: #38BDF8 !important;
    }

    .tool-call-content .hljs-literal,
    .tool-result-content .hljs-literal {
        color: #F59E0B !important;
    }

    .tool-call-content .hljs-punctuation,
    .tool-result-content .hljs-punctuation {
        color: #94A3B8 !important;
    }

    .tool-call-content .hljs-property,
    .tool-result-content .hljs-property {
        color: #38BDF8 !important;
    }

    .tool-call-content .hljs-attr,
    .tool-result-content .hljs-attr {
        color: #8B5CF6 !important;
    }
    </style>
  </body>
</html>
